//
//  SuperTuxKart - a fun racing game with go-kart
//  Copyright (C) 2009-2013 Joerg Henrichs
//
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License
//  as published by the Free Software Foundation; either version 3
//  of the License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

#ifndef HEADER_IRR_DRIVER_HPP
#define HEADER_IRR_DRIVER_HPP

/**
 * \defgroup graphics
 * This module contains the core graphics engine, that is mostly a thin layer
 * on top of irrlicht providing some additional features we need for STK
 * (like particles, more scene node types, mesh manipulation tools, material
 * management, etc...)
 */

#include <string>
#include <vector>

#include <IVideoDriver.h>
#include <vector2d.h>
#include <dimension2d.h>
#include <SColor.h>
#include "IrrlichtDevice.h"
#include "ISkinnedMesh.h"
#include <ISceneNode.h>
#include <IAnimatedMeshSceneNode.h>
#include <ISceneManager.h>


#include "../io/file_manager.h"
#include "../utils/aligned_array.h"
#include "../utils/no_copy.h"
#include "shaders.h"
#include "../utils/ptr_vector.h"
//#include "../utils/vec3.h"


namespace irr
{
    namespace scene { class ISceneManager; class IMesh; class IAnimatedMeshSceneNode; class IAnimatedMesh;
        class IMeshSceneNode; class IParticleSystemSceneNode; class ICameraSceneNode; class ILightSceneNode;
        class CLensFlareSceneNode; }
    namespace gui   { class IGUIEnvironment; class IGUIFont; }
}
using namespace irr;




class RTT;
class FrameBuffer;
class ShadowImportanceProvider;
class AbstractKart;
class Camera;
class PerCameraNode;
class PostProcessing;
class LightNode;
class ShadowImportance;

enum LSRenderingPass
{
    SOLID_NORMAL_AND_DEPTH_PASS,
    SOLID_LIT_PASS,
    TRANSPARENT_PASS,
    GLOW_PASS,
    SHADOW_PASS,
    PASS_COUNT,
};

enum TypeFBO
{
    FBO_SSAO,
    FBO_NORMAL_AND_DEPTHS,
    FBO_COMBINED_DIFFUSE_SPECULAR,
    FBO_COLORS,
    FBO_DIFFUSE,
    FBO_SPECULAR,
    FBO_MLAA_COLORS,
    FBO_MLAA_BLEND,
    FBO_MLAA_TMP,
    FBO_TMP1_WITH_DS,
    FBO_TMP2_WITH_DS,
    FBO_TMP4,
    FBO_LINEAR_DEPTH,
    FBO_HALF1,
    FBO_HALF1_R,
    FBO_HALF2,
    FBO_HALF2_R,
    FBO_QUARTER1,
    FBO_QUARTER2,
    FBO_EIGHTH1,
    FBO_EIGHTH2,
    FBO_DISPLACE,
    FBO_BLOOM_1024,
    FBO_SCALAR_1024,
    FBO_BLOOM_512,
    FBO_TMP_512,
    FBO_LENS_512,

    FBO_BLOOM_256,
    FBO_TMP_256,
    FBO_LENS_256,

    FBO_BLOOM_128,
    FBO_TMP_128,
    FBO_LENS_128,
    FBO_COUNT
};

enum TypeRTT
{
    RTT_TMP1 = 0,
    RTT_TMP2,
    RTT_TMP3,
    RTT_TMP4,
    RTT_LINEAR_DEPTH,
    RTT_NORMAL_AND_DEPTH,
    RTT_COLOR,
    RTT_DIFFUSE,
    RTT_SPECULAR,


    RTT_HALF1,
    RTT_HALF2,
    RTT_HALF1_R,
    RTT_HALF2_R,

    RTT_QUARTER1,
    RTT_QUARTER2,
    //    RTT_QUARTER3,
    //    RTT_QUARTER4,

    RTT_EIGHTH1,
    RTT_EIGHTH2,

    //    RTT_SIXTEENTH1,
    //    RTT_SIXTEENTH2,

    RTT_SSAO,

    //    RTT_COLLAPSE,
    //    RTT_COLLAPSEH,
    //    RTT_COLLAPSEV,
    //    RTT_COLLAPSEH2,
    //    RTT_COLLAPSEV2,
    //    RTT_WARPH,
    //    RTT_WARPV,

    //    RTT_HALF_SOFT,

    RTT_DISPLACE,
    RTT_MLAA_COLORS,
    RTT_MLAA_BLEND,
    RTT_MLAA_TMP,

    RTT_BLOOM_1024,
    RTT_SCALAR_1024,
    RTT_BLOOM_512,
    RTT_TMP_512,
    RTT_LENS_512,
    RTT_BLOOM_256,
    RTT_TMP_256,
    RTT_LENS_256,
    RTT_BLOOM_128,
    RTT_TMP_128,
    RTT_LENS_128,

    RTT_COUNT
};

/**
  * \brief class that creates the irrLicht device and offers higher-level
  *  ways to manage the 3D scene
  * \ingroup graphics
  */
class IrrDriver : public IEventReceiver, public NoCopy
{
private:
    int m_gl_major_version, m_gl_minor_version;
    bool hasVSLayer;
    bool hasBaseInstance;
    bool hasDrawIndirect;
    bool hasBuffserStorage;
    bool hasComputeShaders;
    bool hasTextureStorage;
    bool hasTextureView;

    GLsync m_sync;
    /** The irrlicht device. */
    IrrlichtDevice             *m_device;
    /** Irrlicht scene manager. */
    scene::ISceneManager       *m_scene_manager;
    /** Irrlicht gui environment. */
    gui::IGUIEnvironment       *m_gui_env;
    /** Irrlicht video driver. */
    video::IVideoDriver        *m_video_driver;
    /** Post-processing. */
        /** Irrlicht race font. */
    gui::IGUIFont              *m_race_font;
    /** Post-processing. */
    PostProcessing             *m_post_processing;
    /** Shaders. */
    Shaders              *m_shaders;
        /** RTTs. */
    RTT                *m_rtts;
    std::vector<core::matrix4> sun_ortho_matrix;
    core::vector3df    rh_extend;
    core::matrix4      rh_matrix;
    core::matrix4      rsm_matrix;
    core::vector2df    m_current_screen_size;
    core::dimension2du m_actual_screen_size;



    std::string                 m_texture_error_message;





    /** Matrixes used in several places stored here to avoid recomputation. */
    core::matrix4 m_ViewMatrix, m_InvViewMatrix, m_ProjMatrix, m_InvProjMatrix, m_ProjViewMatrix, m_previousProjViewMatrix, m_InvProjViewMatrix;



    float blueSHCoeff[9];
    float greenSHCoeff[9];
    float redSHCoeff[9];

    /** Keep a trace of the origin file name of a texture. */
    std::map<video::ITexture*, std::string> m_texturesFileName;

    /** Flag to indicate if a resolution change is pending (which will be
     *  acted upon in the next update). None means no change, yes means
     *  change to new resolution and trigger confirmation dialog.
     *  Cancel indicates a change of the resolution (back to the original
     *  one), but no confirmation dialog. */
    enum {RES_CHANGE_NONE, RES_CHANGE_YES,
          RES_CHANGE_CANCEL}                m_resolution_changing;

public:
    GLuint SkyboxCubeMap;
    GLuint SkyboxSpecularProbe;
    /** A simple class to store video resolutions. */
    class VideoMode
    {
    private:
        int m_width;
        int m_height;
    public:
        VideoMode(int w, int h) {m_width=w; m_height=h; }
        int getWidth() const  {return m_width;  }
        int getHeight() const {return m_height; }
    };   // VideoMode

    struct BloomData {
        scene::ISceneNode * node;
        float power;
    };

    video::SColorf getAmbientLight() const;

    struct GlowData {
        scene::ISceneNode * node;
        float r, g, b;
    };

private:
    std::vector<VideoMode> m_modes;

    unsigned             object_count[PASS_COUNT];
    unsigned             poly_count[PASS_COUNT];
    u32                  m_renderpass;

    LSRenderingPass m_phase;


    /** Whether the mouse cursor is currently shown */
    bool                  m_pointer_shown;
    void                 applyResolutionSettings();
    void                 createListOfVideoModes();
    void renderFixed(float dt);
    void renderGLSL(float dt);
    void renderSolidFirstPass();
    void renderSolidSecondPass();
    void renderNormalsVisualisation();
    void renderTransparent();
    void renderParticles();
    void computeSunVisibility();
    void renderShadows();
    void renderRSM();

    void PrepareDrawCalls(scene::ICameraSceneNode *camnode);

public:

         IrrDriver();
        ~IrrDriver();
    void initDevice();

    void test_Render();
    void test_Render2();
    void test_CreateMap();
    void setMaxTextureSize();
    void getOpenGLData(std::string *vendor, std::string *renderer,
                       std::string *version);
    void reset();


    void updateConfigIfRelevant();
    void setAllMaterialFlags(scene::IMesh *mesh) const;
    scene::IAnimatedMesh *getAnimatedMesh(const std::string &name);
    scene::IMesh         *getMesh(const std::string &name);
    video::ITexture      *applyMask(video::ITexture* texture,
                                    const std::string& mask_path);
    void displayFPS();
    bool                  OnEvent(const irr::SEvent &event);
    video::ITexture      *getTexture(FileManager::AssetType type,
                                     const std::string &filename,
                                     bool is_premul=false,
                                     bool is_prediv=false,
                                     bool complain_if_not_found=true);
    video::ITexture      *getTexture(const std::string &filename,
                                     bool is_premul=false,
                                     bool is_prediv=false,
                                     bool complain_if_not_found=true);
    void                  clearTexturesFileName();
    std::string           getTextureName(video::ITexture* tex);
    void                  grabAllTextures(const scene::IMesh *mesh);
    void                  dropAllTextures(const scene::IMesh *mesh);
    scene::IMesh         *createQuadMesh(const video::SMaterial *material=NULL,
                                         bool create_one_quad=false);
    scene::IMesh         *createTexturedQuadMesh(const video::SMaterial *material,
                                                 const double w, const double h);
    scene::ISceneNode    *addWaterNode(scene::IMesh *mesh, scene::IMesh **welded,
                                       float wave_height,
                                       float wave_speed, float wave_length);
    scene::IMeshSceneNode*addOctTree(scene::IMesh *mesh);
    scene::IMeshSceneNode*addSphere(float radius,
                 const video::SColor &color=video::SColor(128, 255, 255, 255));
    scene::IMeshSceneNode*addMesh(scene::IMesh *mesh,
                                  const std::string& debug_name,
                                  scene::ISceneNode *parent=NULL);

    void setPhase(LSRenderingPass);
    LSRenderingPass getPhase() const;
    void IncreaseObjectCount();
    void IncreasePolyCount(unsigned);
    core::array<video::IRenderTarget> &getMainSetup();


    void                  removeNode(scene::ISceneNode *node);
    void                  removeMeshFromCache(scene::IMesh *mesh);
    void                  removeTexture(video::ITexture *t);
    scene::IAnimatedMeshSceneNode
        *addAnimatedMesh(scene::IAnimatedMesh *mesh, const std::string& debug_name, scene::ISceneNode* parent = NULL);
    scene::ICameraSceneNode
                         *addCameraSceneNode();
    Camera               *addCamera(unsigned int index);
    void                  removeCameraSceneNode(scene::ICameraSceneNode *camera);
    void                  removeCamera(Camera *camera);
    void                  update(float dt);
    /** Call to change resolution */

    void                  changeResolution(const int w, const int h, const bool fullscreen);
  /** Call this to roll back to the previous resolution if a resolution switch attempt goes bad */
    void                  cancelResChange();
    bool                  moveWindow(int x, int y);

    void                  showPointer();
    void                  hidePointer();
    bool                  isPointerShown() const { return m_pointer_shown; }
    core::position2di     getMouseLocation();

    void                  setTextureErrorMessage(const std::string &error,
                                                 const std::string &detail="");
    void                  unsetTextureErrorMessage();
    class GPUTimer        &getGPUTimer(unsigned);



    // ------------------------------------------------------------------------
    /** Convenience function that loads a texture with default parameters
     *  but includes an error message.
     *  \param filename File name of the texture to load.
     *  \param error Error message, potentially with a '%' which will be replaced
     *               with detail.
     *  \param detail String to replace a '%' in the error message.
     */
    video::ITexture* getTexture(const std::string &filename,
                                const std::string &error_message,
                                const std::string &detail="")
    {
        setTextureErrorMessage(error_message, detail);
        video::ITexture *tex = getTexture(filename);
        unsetTextureErrorMessage();
        return tex;
    }   // getTexture

    // ------------------------------------------------------------------------
    /** Convenience function that loads a texture with default parameters
     *  but includes an error message.
     *  \param filename File name of the texture to load.
     *  \param error Error message, potentially with a '%' which will be replaced
     *               with detail.
     *  \param detail String to replace a '%' in the error message.
     */
    video::ITexture* getTexture(const std::string &filename,
                                char *error_message,
                                char *detail=NULL)
    {
        if(!detail)
            return getTexture(filename, std::string(error_message),
                              std::string(""));

        return getTexture(filename, std::string(error_message),
                          std::string(detail));
    }   // getTexture

    // ------------------------------------------------------------------------
    /** Returns the currently defined texture error message, which is used
     *  by event_handler.cpp to print additional info about irrlicht
     *  internal errors or warnings. If no error message is currently
     *  defined, the error message is "".
     */
    const std::string &getTextureErrorMessage()
    {
        return m_texture_error_message;
    }   // getTextureErrorMessage

   // ------------------------------------------------------------------------
    void setRTT(RTT* rtt);
    // ------------------------------------------------------------------------
    RTT* getRTT() { return m_rtts; }


    // ------------------------------------------------------------------------
    /** Returns the currently defined texture error message, which is used
     *  by event_handler.cpp to print additional info about irrlicht
     *  internal errors or warnings. If no error message is currently
     *  defined, the error message is "".
     */


    /** Returns the frame size. */
    const core::dimension2d<u32>& getFrameSize() const
                       { return m_video_driver->getCurrentRenderTargetSize(); }
    // ------------------------------------------------------------------------
    /** Returns the irrlicht device. */
    IrrlichtDevice       *getDevice()       const { return m_device;        }
    // ------------------------------------------------------------------------
    /** Returns the irrlicht video driver. */
    video::IVideoDriver  *getVideoDriver()  const { return m_video_driver;  }
    // ------------------------------------------------------------------------
    /** Returns the irrlicht scene manager. */
    scene::ISceneManager *getSceneManager() const { return m_scene_manager; }
    // ------------------------------------------------------------------------
    /** Returns the gui environment, used to add widgets to a screen. */
    gui::IGUIEnvironment *getGUI() const { return m_gui_env; }
        // ------------------------------------------------------------------------
    /** Returns the current real time, which might not be 0 at start of the
     *  application. Value in msec. */
    unsigned int getRealTime() {return m_device->getTimer()->getRealTime(); }
    // ------------------------------------------------------------------------
    /** Returns a pointer to the post processing object. */
    inline PostProcessing* getPostProcessing()  {return m_post_processing;}

        // -----------------------------------------------------------------------
    inline video::E_MATERIAL_TYPE getShader(const ShaderType num)  {return m_shaders->getShader(num);}


    const core::vector2df &getCurrentScreenSize() const { return m_current_screen_size; }
    const core::dimension2du getActualScreenSize() const { return m_actual_screen_size; }

    core::array<video::IRenderTarget> m_mrt;
    GLuint getRenderTargetTexture(TypeRTT which);

        // --------------------- OLD RTT --------------------
    /**
      * THIS IS THE OLD OPENGL 1 RTT PROVIDER, USE THE SHADER-BASED
      * RTT FOR NEW DEVELOPMENT
      *
      * Class that provides RTT (currently, only when no other 3D rendering
      * in the main scene is required)
      * Provides an optional 'setupRTTScene' method to make it quick and easy
      * to prepare rendering of 3D objects but you can also manually set the
      * scene/camera. If you use the factory 'setupRTTScene', cleanup can be
      * done through 'tearDownRTTScene' (destructor will also do this). If
      * you set it up manually, you need to clean it up manually.
      */
         // ------------------------------------------------------------------------
    void applyObjectPassShader();
    void applyObjectPassShader(scene::ISceneNode * const node,
                               bool rimlit = false);

    void onLoadWorld();
    void onUnloadWorld();

    void renderScene(scene::ICameraSceneNode * const camnode,
                     unsigned pointlightcount,
                     float dt, bool hasShadows, bool forceRTT);

    void computeMatrixesAndCameras(scene::ICameraSceneNode * const camnode,
                                   size_t width, size_t height);

    class RTTProvider
    {
        /** A pointer to texture on which a scene is rendered. Only used
         *  in between beginRenderToTexture() and endRenderToTexture calls. */
        video::ITexture            *m_render_target_texture;

        bool                        m_persistent_texture;

        /** Main node of the RTT scene */
        scene::ISceneNode          *m_rtt_main_node;

        scene::ICameraSceneNode    *m_camera;

        scene::ILightSceneNode     *m_light;

        /** Irrlicht video driver. */
        video::IVideoDriver        *m_video_driver;

    public:
        RTTProvider(const core::dimension2du &dimension,
                    const std::string &name, bool persistent_texture);

        ~RTTProvider();

        /**
          * \brief Quick utility method to setup a scene from a plain list
          *  of models
          *
          * Sets up a given vector of meshes for render-to-texture. Ideal to
          * embed a 3D object inside the GUI. If there are multiple meshes,
          * the first mesh is considered to be the root, and all following
          * meshes will have their locations relative to the location of the
          * first mesh.
          *
          * \param mesh             The list of meshes to add to the scene
          * \param mesh_location    Location of each fo these meshes
          * \param model_frames     For animated meshes, which frame to use
          *                         (value can be -1 to set none)
          *                         When frame is not -1, the corresponding
          *                         IMesh must be an IAnimatedMesh.
          * \pre           The 3 vectors have the same size.
          */

        /** Optional 'angle' parameter will rotate the object added
         *  *through setupRTTScene* */
        video::ITexture* renderToTexture(float angle=-1,
                                         bool is_2d_render=false);

        void tearDownRTTScene();

    };


};   // IrrDriver

extern IrrDriver *irr_driver;

#endif   // HEADER_IRR_DRIVER_HPP
